//-------------------------------------------------------------------------
// File: Process.cpp
//
// Descr.: - todo -
//
// Author: Stefan Leihkauf
// Date:   17.08.2011
//-------------------------------------------------------------------------
#include "cb_pch.h"
#include "process/Process.h"
#include <Psapi.h>

namespace cb { namespace process {

//-------------------------------------------------------------------------
/**
*/
Process::StartupInfo::StartupInfo() :
	RedirectStandardError(false),
	RedirectStandardInput(false),
	RedirectStandardOutput(false)
{
}

//-------------------------------------------------------------------------
/**
*/
Process::Process() :
_processPriority(Normal)
{
}

//-------------------------------------------------------------------------
/**
*/
Process::~Process()
{
}

//-------------------------------------------------------------------------
/**
*/
bool Process::Start()
{
	return StartProcess();
}

//-------------------------------------------------------------------------
/**
*/
bool Process::Start(const Process::StartupInfo& startupInfo)
{
	this->_startupInfo = startupInfo;
	return StartProcess();
}

//-------------------------------------------------------------------------
/**
*/
bool Process::Start(const StdString& filename)
{
	_startupInfo.ApplicationName = filename;
	_startupInfo.CommandLine.clear();
	return StartProcess();
}

//-------------------------------------------------------------------------
/**
*/
bool Process::Start(const StdString& filename, const StdString& arguments)
{
	_startupInfo.ApplicationName = filename;
	_startupInfo.CommandLine = arguments;
	return StartProcess();
}

//-------------------------------------------------------------------------
/**
*/
Process::StartupInfo& Process::GetStartupInfo()
{
	return this->_startupInfo;
}

//-------------------------------------------------------------------------
/**
*/
bool Process::StartProcess()
{
	// TODO: ...check if process allready running

	/* reset all internal structures */
	_processInformation.Reset();

	if(_startupInfo.ApplicationName.empty() && _startupInfo.CommandLine.empty())
		return false;

	StdString appName = _startupInfo.ApplicationName;
	StdString cmdLine;

	/* ensure propper quotation 
	*/
	appName = buildApplicationName(appName);

	/* If the application to start is a batch file we have to start the command interpreter ("cmd.exe") and
	   pass the batch filename via the commandline.
	*/
	if(isBatchScript(appName)) {
		cmdLine = _T("/c ");
		cmdLine.append(appName + _T(" "));
		appName = _T("cmd.exe");
	}

	cmdLine.append(_startupInfo.CommandLine);

	/*  The commandline may be modified by the CreateProcess API so it can not be an
	    static allocated buffer. A character array is built here which size depends on the
		unicode settings of the application.
	*/
	PtrTCharBuffer ptr_command_line = buildCommandLine(cmdLine);

	/* The CreateProcess API excpects an untyped (void) pointer to an character array as the
	   process environment. In the case of unicode this is a pointer to an array of unicode characters.
	*/
	PtrByteBuffer ptr_env_block = buildEnvironmentBlock(_startupInfo.EnvironmentBlock);


	/* We start by NOT using any security descriptors. (until I understand them)

	SECURITY_ATTRIBUTES processAttributes;
	SECURITY_ATTRIBUTES threadAttributes;
	*/

	/* The created process may inherit handles from the calling process. (I don't see why not) */
	BOOL bInheritHandles = TRUE;

	/* Process creation flags and priority class */
	_processPriority = Normal;
	DWORD creationFlags = 0L;

#if _UNICODE
	creationFlags |= CREATE_UNICODE_ENVIRONMENT;
#endif

	/* STARTINFO */
	STARTUPINFO startupInfo;
	memset(&startupInfo, 0, sizeof(STARTUPINFO));
	startupInfo.cb = sizeof(STARTUPINFO);

	if(_startupInfo.RedirectStandardInput) {
	}

	PROCESS_INFORMATION processInformation;

	//BOOL b = CreateProcess(
	//	_startupInfo.ApplicationName.empty() ? 0 : _startupInfo.ApplicationName.c_str(), /*application name*/
	//	saCommandLine ? saCommandLine.get() : 0,						/*commandline*/
	//	0,																/*process security attributes*/
	//	0,																/*thread security attributes*/
	//	bInheritHandles,												/*inherit handles?*/
	//	creationFlags,													/*creation flags (and priority)*/
	//	environmentBlock.empty() ? NULL : environmentBlock.data(),		/*environment*/
	//	_startupInfo.WorkingDirectory.empty() ? NULL : _startupInfo.WorkingDirectory.c_str(),	/*current dirctory*/
	//	&startupInfo,													/*startup info*/
	//	&processInformation												/*process information*/
	//	);

	//if(TRUE == b) {
	//	/* save startup information */
	//	// ...todo

	//	/* save process information */
	//	_processInformation.HandleProcess.reset(processInformation.hProcess);
	//	_processInformation.HandleThread.reset(processInformation.hThread);
	//	_processInformation.ProcessId = processInformation.dwProcessId;
	//	_processInformation.ThreadId = processInformation.dwThreadId;
	//}

	return false;
}

//-------------------------------------------------------------------------
/**
*/
Process::PtrByteBuffer Process::buildEnvironmentBlock(const EnvironmentBlock& envBlock)
{
	PtrByteBuffer ptrBuffer;

	if(!envBlock.empty()) {
		static const unsigned string_value_size = sizeof(EnvironmentBlock::StringList::value_type::value_type);
		EnvironmentBlock::StringList envList = envBlock.toString();

		// compute bytesize of environment block
		unsigned bytesize = 0;
		for(unsigned i = 0; i < envList.size(); i++) {
			bytesize += (envList[i].length() + 1) * string_value_size;
		}

		ptrBuffer.reset(new char [bytesize]);

		// copy bytes
		unsigned offset = 0;
		for(unsigned i = 0; i < envList.size(); i++) {
			unsigned const num_bytes = (envList[i].length() +1) * string_value_size;
			memcpy(ptrBuffer.get() + offset, envList[i].c_str(), num_bytes);
			offset += num_bytes;
		}
	}

	return ptrBuffer;
}

//-------------------------------------------------------------------------
/**
*/
Process::PtrTCharBuffer Process::buildCommandLine(const StdString& cmdLine)
{
	PtrTCharBuffer ptrBuffer;

	if(!cmdLine.empty()) {
		ptrBuffer.reset(new PtrTCharBuffer::element_type [cmdLine.length() + 1]);
		cmdLine.copy(ptrBuffer.get(), cmdLine.length(), 0);
		ptrBuffer[cmdLine.length()] = _T('\0');
	}
	return ptrBuffer;
}

//-------------------------------------------------------------------------
/**
*/
StdString Process::buildApplicationName(const StdString& appName)
{
	StdString retName;

	if(appName.empty())
		return retName;

	/* ensure that the pathname is quoted if whitespaces are present
	*/
	if(appName.find(_T(' ')) != StdString::npos) {
		if(appName.front() != _T('\"')) // leading quote
			retName = _T("\"") + appName;

		if(appName.back() != _T('\"')) // ending quote
			retName.append(_T("\""));
	} else
		retName = appName;

	return retName;
}

//-------------------------------------------------------------------------
/**
*/
bool Process::isBatchScript(const StdString& appName) const
{
    size_t pos = StdString::npos;

    if ((pos = appName.find_last_of(_T("."))) != StdString::npos) {
        StdString sub = appName.substr(pos);
        if (sub.back() == _T('\"')) // remove trailing quote
            sub.erase(sub.length() - 1);

        if (sub == _T(".bat"))
            return true;
    }
    return false;
}

}} // namespace cb::process
